package gui;

import java.awt.*;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;

import fiziksobject.FiziksBall;
import fiziksobject.Springs;
import fiziksobject.magneticField;

import physics.Forcer;
import physics.Location;
import physics.PhysicsEngine;
import physics.Vector;
import physics.physicsobject;
import physics.Vector.Type;

public class FiziksWorld extends JComponent implements MouseListener, MouseMotionListener {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private PhysicsSettings settings;
	private Location max;
	private FiziksBall selectedBall;

	public FiziksWorld(PhysicsSettings s, Location max) {
		this.max = max;
		settings = s;
		PhysicsEngine.getPhysicsEngine().addForcer(new WallForcer());
		addMouseListener(this);
		addMouseMotionListener(this);
	}

	@Override
	public Dimension getPreferredSize() {
		return new Dimension((int) (max.getX() * settings.getScale()),
				(int) (max.getY() * settings.getScale()));
	}

	public void start() {
		PhysicsEngine.getPhysicsEngine().startEngine();
	}

	public void pause() {
		PhysicsEngine.getPhysicsEngine().pauseEngine();
	}

	@Override
	public void paint(Graphics g) {/*
		double maxX = 0;
		double maxY = 0;
		for(physicsobject po : PhysicsEngine.getPhysicsEngine().getPhysicsObjects()) {
			FiziksBall fb = (FiziksBall) po;
			double rad = fb.getRadius();
			maxX = Math.max(po.getLoc().getX()+rad,maxX);
			maxY = Math.max(po.getLoc().getY()+rad,maxY);
		}
		if(getSize().getWidth()<maxX*settings.getScale()) {
			max = new Location(maxX,max.getY());
		}
		if(getSize().getHeight()<maxY*settings.getScale()) {
			max = new Location(max.getX(),maxY);
		}*/
		if (getSize().getHeight() > max.getY() * settings.getScale()) {
			max = new Location(max.getX(), getSize().getHeight()
					/ settings.getScale());
		}
		if (getSize().getWidth() > max.getX() * settings.getScale()) {
			max = new Location(getSize().getWidth() / settings.getScale(), max
					.getY());
		}
		Main.jsp.revalidate();
		g.setColor(Color.WHITE);
		g.fillRect(0, 0, getWidth(), getHeight());
		ArrayList<physicsobject> al = PhysicsEngine.getPhysicsEngine().getPhysicsObjects();
		Graphics g2 = g.create();
		g2.translate(0, getHeight());
		for (physicsobject po : al) {
			draw(po, g2);
			Location loc = po.getLoc();
			// show momentum arrows
			Location endmom = loc.addVector(po.getMomentum().divide(5));
			// GraphicsUtil.drawArrow(g2,
			// (int)(loc.getX()*settings.getScale()),-(int)(loc.getY()*settings.getScale()),(int)(endmom.getX()*settings.getScale()),-(int)(endmom.getY()*settings.getScale()));
			// show force arrows
			ArrayList<Forcer> combo = PhysicsEngine.getPhysicsEngine().getForcers();
			combo.addAll(PhysicsEngine.getPhysicsEngine().getPhysicsObjects());
			((Graphics2D) g2).setStroke(new BasicStroke(2));
			if (settings.getShowForces()) {
				for (Forcer forcer : combo) {
					if (forcer == po) {
						continue;
					}
					Vector[] forces = forcer.getForces(po);
					for (Vector force : forces) {
						Location end = loc.addVector(force.divide(5));
						g2.setColor(settings.getColor(force.getType()));
						GraphicsUtil.drawArrow(g2, (int) (loc.getX() * settings
								.getScale()), -(int) (loc.getY() * settings
								.getScale()), (int) (end.getX() * settings
								.getScale()), -(int) (end.getY() * settings
								.getScale()));
						g2.setColor(Color.BLACK);
					}
				}
			}
		}
		for(Forcer f : PhysicsEngine.getPhysicsEngine().getForcers()){
			drawForcer(f, g);
		}
		// show settings.getScale()
		g.setColor(Color.BLACK);
		g.drawLine(10, 20, 210, 20);
		g.drawLine(10, 10, 10, 30);
		g.drawLine(210, 10, 210, 30);
		g.drawString((100d / settings.getScale()) + " m", 20, 40);
	}
	private void drawForcer(Forcer f, Graphics g){
		if(f instanceof magneticField){/*
			magneticField fx = (magneticField) f;

			int xMax = this.getHeight();
			int yMax = this.getWidth();
			int x = this.getX();
			int y = this.getY();
				for(int i = x; i<xMax; i++){
					for(int j = y; j<yMax; j++){
						if(fx.getInto()){
						GraphicsUtil.drawX(g, x, y, 5);
						}
						else{
							g.fillOval(x, y, 5, 5);
						}
					}
				
			}*/
		}
		else if(f instanceof Springs) {
			Springs spring = (Springs) f;
			double curviness = 100;
			physicsobject po = spring.getMassOnSpring();
			if(po==null) {
				return;
			}
			double k = spring.getspringConstant();
			double poX = po.getLoc().getX()*settings.getScale();
			double poY = po.getLoc().getY()*settings.getScale();
			double springX = spring.getSpringLoc().getX()*settings.getScale();
			double springY = spring.getSpringLoc().getY()*settings.getScale();
			Vector line = new Vector(null,null,springX-poX,springY-poY).unit();
			Vector dir = new Vector(null,null,-line.getYMag(),line.getXMag()).multiply(50);
			double b = 2*Math.PI/(curviness/k);
			double x = poX, y = poY;
			for(double t=1;t<=curviness;t++) {
				Vector point = dir.multiply(Math.sin(b*t));
				double newX = poX+t*(springX-poX)/curviness+point.getXMag();
				double newY = poY+t*(springY-poY)/curviness+point.getYMag();
				g.setColor(Color.BLACK);
				g.drawLine((int)x,getHeight()-(int)y,(int)newX,getHeight()-(int)newY);
				x = newX; y = newY;
			}
			
		}
	}
	private void draw(physicsobject po, Graphics g) {
		if (po instanceof FiziksBall) {
			Location loc = po.getLoc();
			double radius = ((FiziksBall) po).getRadius() * settings.getScale()
					* 2;
			g.setColor(po.getColor());
			// System.out.println(loc);
			int x = (int) ((loc.getX() * settings.getScale() - (radius / 2)));
			int y = (int) ((loc.getY() * (-settings.getScale()) - (radius / 2)));
			// System.out.println(x+" "+y+" "+radius);
			g.fillOval(x, y, (int) radius, (int) radius);
		}
	}

	class WallForcer implements Forcer {
		public Vector[] getForces(physicsobject po) {
			Vector[] ret = new Vector[1];
			Location loc = po.getLoc();
			Vector momentum = po.getMomentum();
			FiziksBall p = (FiziksBall) po;
			double radius = p.getRadius();
			ret[0] = new Vector(loc, Vector.Type.FORCE_NORMAL, 0, 0);
			if (loc.getX() + radius >= max.getX() || loc.getX() - radius <= 0) {
				ret[0] = ret[0].add(new Vector(loc, Vector.Type.FORCE_NORMAL,
						-momentum.getXMag() * 2, 0)
						.divide(PhysicsEngine.TIMESTEP));
				po.setLoc(loc.getX() - radius <= 0 ? new Location(radius, loc
						.getY())
						: new Location(max.getX() - radius, loc.getY()));
			}
			if (loc.getY() + radius >= max.getY() || loc.getY() - radius <= 0) {
				ret[0] = ret[0].add(new Vector(loc, Vector.Type.FORCE_NORMAL,
						0, -momentum.getYMag() * 2)
						.divide(PhysicsEngine.TIMESTEP));
				po
						.setLoc(loc.getY() - radius <= 0 ? new Location(loc
								.getX(), radius) : new Location(loc.getX(), max
								.getY()
								- radius));
			}
			return ret;
		}
	}
	
	private FiziksBall ballAtLoc(int x,int y) {
		FiziksBall selecting = null;
		for(physicsobject po : PhysicsEngine.getPhysicsEngine().getPhysicsObjects()) {
			Location loc = new Location(x/settings.getScale(),max.getY()-y/settings.getScale());
			//System.out.println(loc);
			//System.out.println(po.getLoc());
			if(po.getLoc().distanceTo(loc)<((FiziksBall) po).getRadius()) {
				selecting = (FiziksBall) po;
			}
		}	
		return selecting;
	}

	public void mouseClicked(final MouseEvent e) {
		if(PhysicsEngine.getPhysicsEngine().isRunning()) {
			return;
		}
		switch (e.getButton()) {
		case 1:
			PhysicsEngine pe = PhysicsEngine.getPhysicsEngine();
			double x = e.getX();
			double y = e.getY() - (getHeight());
			x /= settings.getScale();
			y /= -settings.getScale();
			double xMag = (Math.random() - 0.5) * 50d;
			double yMag = (Math.random() - 0.5) * 50d;
			FiziksBall fb = new FiziksBall(1d, 0.00, new Vector(new Location(x,
					y), null, xMag, yMag), new Location(x, y), 1);
			PhysicsEngine.getPhysicsEngine().addPhysicsObject(fb);
			repaint();
			break;
		case 3:
			//are we right clicking on something?
			FiziksBall selecting = ballAtLoc(e.getX(),e.getY());
			System.out.println(selecting);
			if(selecting!=null) {
				JPopupMenu rightClickObject = new JPopupMenu();
				JMenuItem edit = new JMenuItem("Edit");
				rightClickObject.add(edit);
				edit.addActionListener(new EditListener(selecting));
				JMenuItem delete = new JMenuItem("Delete");
				rightClickObject.add(delete);
				delete.addActionListener(new DeleteListener(selecting));
				rightClickObject.show(this, e.getX(), e.getY());
				rightClickObject.setVisible(true);
			}
			else {
				JPopupMenu rightClick = new JPopupMenu();
				JMenuItem createBall = new JMenuItem("Create Ball");
				createBall.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent ae) {
						FiziksBall fb = new FiziksBall(1,0,new Vector().toType(Type.MOMENTUM),new Location(e.getX()/settings.getScale(),max.getY()-e.getY()/settings.getScale()),1);
						PhysicsEngine.getPhysicsEngine().addPhysicsObject(fb);
						FiziksBallProperties fbp = new FiziksBallProperties(fb);
						fbp.setVisible(true);
						fbp.setSize(400,500);
					}
				});
				rightClick.add(createBall);
				rightClick.show(this,e.getX(),e.getY());
				rightClick.setVisible(true);
			}
		}
	}
	
	private class EditListener implements ActionListener {
		private FiziksBall edit;
		public EditListener(FiziksBall fb) {
			edit = fb;
		}
		public void actionPerformed(ActionEvent ae) {
			FiziksBallProperties fbp = new FiziksBallProperties(edit);
			fbp.setVisible(true);
			fbp.setSize(400,500);
		}
	}
	private class DeleteListener implements ActionListener {
		private FiziksBall edit;
		public DeleteListener(FiziksBall fb) {
			edit = fb;
		}
		public void actionPerformed(ActionEvent ae) {
			PhysicsEngine.getPhysicsEngine().removePhysicsObject(edit);
		}
	}

	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	public void mousePressed(MouseEvent e) {
		selectedBall = ballAtLoc(e.getX(),e.getY());
	}

	public void mouseReleased(MouseEvent e) {
		selectedBall = null;
	}

	public void clearPhysicsObjects() {
		PhysicsEngine.getPhysicsEngine().clearPhysicsObjects();
	}

	public Dimension getPreferredScrollableViewportSize() {
		return getPreferredSize();
	}

	public int getScrollableBlockIncrement(Rectangle arg0, int arg1, int arg2) {
		// TODO Auto-generated method stub
		return 1;
	}

	public boolean getScrollableTracksViewportHeight() {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean getScrollableTracksViewportWidth() {
		// TODO Auto-generated method stub
		return false;
	}

	public int getScrollableUnitIncrement(Rectangle arg0, int arg1, int arg2) {
		// TODO Auto-generated method stub
		return 1;
	}
	
	private long lastDrag = 0;

	public void mouseDragged(MouseEvent e) {
		if(PhysicsEngine.getPhysicsEngine().isRunning()) {
			return;
		}
		double x = e.getX()/settings.getScale();
		double y = max.getY()-(e.getY()/settings.getScale());
		if(selectedBall!=null) {
			long now = System.currentTimeMillis();
			Location old = selectedBall.getLoc();
			selectedBall.setLoc(new Location(x,y));
			//selectedBall.setMomentum(new Vector(new Location(),Type.MOMENTUM,x-old.getX(),y-old.getY()).multiply(1000d*selectedBall.getMass()/(now-lastDrag)));
			lastDrag = now;
		}
	}

	public void mouseMoved(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}
}
